Designing a Hohmann Transfer — Beyond the Equation

This page develops the classical Hohmann transfer as a full engineering walkthrough rather than a short textbook note, connecting assumptions, equations, worked examples, Python implementation, and mission interpretation in one coherent flow.

The Hohmann transfer is one of the most important orbit-transfer concepts in astrodynamics. Given two circular orbits, the task is to determine the transfer trajectory and the required \(\Delta v\). However, this problem is not only about computing numbers. It is also about understanding how the solution is built, what assumptions make it valid, and how analytical reasoning connects to code and mission design.

As you work through this problem, keep four guiding questions in mind:

The page is developed as a structured engineering walkthrough: Problem → Assumptions → Model → Solution → Code → Insight.

Why This Matters

Why the Hohmann transfer deserves more than a formula box

The Hohmann transfer is not just a method to move between two orbits — it is one of the clearest early examples of optimal reasoning in orbital mechanics under constraints. It teaches that orbital motion is governed by energy, not simply by altitude, and that the idea of “optimal” always depends on the assumptions imposed on the problem.

Every real mission — from orbit raising and deployment to communication missions, constellation management, and interplanetary staging — builds on these foundations. Studying the Hohmann transfer develops intuition about energy-efficient motion in a gravitational field, shows how assumptions shape valid solutions, and demonstrates how analytical equations become practical computational tools.

In that sense, the transfer is more than a classroom result. It is a compact bridge between theory, equations, code, and mission-level engineering judgment.

Roadmap

This problem is solved not as a formula, but as a system design process. The page follows the way an engineer would approach the transfer in practice.

The discussion begins from the general symbolic form \(r_1 \rightarrow r_2\), then moves to specific numerical cases and a reusable computation workflow.

Assumptions

Before writing equations, the assumptions must be stated clearly. Every result that follows depends on them.

Key insight: The Hohmann transfer is optimal only because these assumptions reduce the problem to a constrained two-point transfer in a central gravitational field. Once those assumptions change, the meaning of “optimal” changes as well.

Under this idealized framework, the transfer becomes a two-impulse maneuver connecting two circular energy states through a single intermediate ellipse.

Mathematical Model

Let \(\mu\) be the gravitational parameter of the central body, \(r_1\) the initial orbit radius, and \(r_2\) the final orbit radius. The transfer between these two circular orbits is performed using an elliptical transfer orbit tangent to the first orbit at perigee and tangent to the second orbit at apogee.

1. Circular-orbit velocity

The velocity for a circular orbit of radius \(r\) is

\[ v_c = \sqrt{\frac{\mu}{r}} \]

2. Transfer-ellipse geometry

Because the transfer ellipse touches both circular orbits, its semi-major axis is simply the average of the two orbital radii:

\[ a_t = \frac{r_1 + r_2}{2} \]

3. Vis-viva equation

The orbital speed at any point on an orbit of semi-major axis \(a\) and instantaneous radius \(r\) is given by the vis-viva equation:

\[ v = \sqrt{\mu\left(\frac{2}{r} - \frac{1}{a}\right)} \]

Applying that to the transfer ellipse gives the velocity immediately after the first burn and immediately before the second burn.

4. Transfer velocity at perigee and apogee

\[ v_{t1} = \sqrt{\mu\left(\frac{2}{r_1} - \frac{1}{a_t}\right)} \]
\[ v_{t2} = \sqrt{\mu\left(\frac{2}{r_2} - \frac{1}{a_t}\right)} \]

5. Burn definitions

The first burn injects the spacecraft from the initial circular orbit onto the transfer ellipse. The second burn circularizes the orbit at the final radius.

\[ \Delta v_1 = v_{t1} - v_{c1} \]
\[ \Delta v_2 = v_{c2} - v_{t2} \]
\[ \Delta v_{\text{total}} = \Delta v_1 + \Delta v_2 \]

6. Transfer time

The spacecraft traverses half the transfer ellipse, so the time of flight is half the orbital period of that ellipse:

\[ t = \pi \sqrt{\frac{a_t^3}{\mu}} \]

7. Energy interpretation

The deeper meaning of the maneuver is best understood through specific orbital energy:

\[ \epsilon = -\frac{\mu}{2a} \]

A circular orbit is a specific energy state. The Hohmann transfer minimizes \(\Delta v\) because it connects two circular energy states using a single intermediate ellipse with tangency at both ends. In other words, the maneuver is not simply “raising altitude”; it is performing a controlled transition between orbital energy levels.

Solution Strategy

This is not just a sequence of calculations — it is a controlled transition between two orbital energy states using impulsive inputs at physically optimal locations.

  1. Compute the velocity in the initial circular orbit.
  2. Compute the transfer-orbit velocity at perigee.
  3. Determine the first burn \(\Delta v_1\).
  4. Compute the transfer-orbit velocity at apogee.
  5. Compute the circular velocity in the final orbit.
  6. Determine the second burn \(\Delta v_2\).
  7. Sum the two burns to obtain the total \(\Delta v\).
  8. Compute the transfer time from half the transfer-ellipse period.

The first burn increases the orbit’s energy and reshapes the trajectory into an ellipse. The second burn adds or removes whatever additional energy is needed to make the spacecraft circular at the destination radius. That is why the Hohmann transfer is best viewed as a two-step energy management maneuver, not just a geometric altitude change.

Python: General Reusable Function

This function represents the analytical model translated into a reusable computational tool. The same structure can be embedded in mission design calculators, trade studies, trajectory scripts, or optimization loops where multiple candidate transfers must be evaluated consistently.

import math


def hohmann_transfer(mu, r1, r2):
    """
    Compute the Hohmann transfer between two circular coplanar orbits.

    Parameters
    ----------
    mu : float
        Gravitational parameter of the central body [m^3/s^2]
    r1 : float
        Radius of the initial circular orbit [m]
    r2 : float
        Radius of the final circular orbit [m]

    Returns
    -------
    dict
        Dictionary containing transfer parameters and delta-v values
    """

    # Semi-major axis of transfer ellipse
    a_t = 0.5 * (r1 + r2)

    # Circular orbit velocities
    v_c1 = math.sqrt(mu / r1)
    v_c2 = math.sqrt(mu / r2)

    # Transfer-orbit velocities from vis-viva
    v_t1 = math.sqrt(mu * (2 / r1 - 1 / a_t))   # at perigee
    v_t2 = math.sqrt(mu * (2 / r2 - 1 / a_t))   # at apogee

    # Delta-v requirements
    delta_v1 = abs(v_t1 - v_c1)
    delta_v2 = abs(v_c2 - v_t2)
    delta_v_total = delta_v1 + delta_v2

    # Time of flight = half the period of transfer ellipse
    transfer_time = math.pi * math.sqrt(a_t**3 / mu)

    return {
        "a_t": a_t,
        "v_c1": v_c1,
        "v_t1": v_t1,
        "delta_v1": delta_v1,
        "v_t2": v_t2,
        "v_c2": v_c2,
        "delta_v2": delta_v2,
        "delta_v_total": delta_v_total,
        "transfer_time": transfer_time,
    }


def print_results(results):
    """Pretty-print Hohmann transfer results."""

    print("Hohmann Transfer Results")
    print("-" * 30)
    print(f"Transfer semi-major axis a_t   = {results['a_t'] / 1000:.3f} km")
    print(f"Initial circular velocity v_c1 = {results['v_c1']:.3f} m/s")
    print(f"Transfer velocity at r1 v_t1   = {results['v_t1']:.3f} m/s")
    print(f"First burn Δv1                 = {results['delta_v1']:.3f} m/s")
    print(f"Transfer velocity at r2 v_t2   = {results['v_t2']:.3f} m/s")
    print(f"Final circular velocity v_c2   = {results['v_c2']:.3f} m/s")
    print(f"Second burn Δv2                = {results['delta_v2']:.3f} m/s")
    print(f"Total Δv                       = {results['delta_v_total']:.3f} m/s")
    print(f"Transfer time                  = {results['transfer_time'] / 60:.3f} min")

This general version mirrors the analytical flow directly and is the cleanest form for reuse.

Example 1: LEO Raise

We first apply the model to an orbit-raising case from a 300 km circular low Earth orbit to a 1000 km circular orbit.

Initial orbit

300 km circular LEO

Final orbit

1000 km circular orbit

Earth radius

\(R_E = 6378\,\text{km}\)

Gravitational parameter

\(\mu = 3.986004418 \times 10^{14}\,\text{m}^3/\text{s}^2\)

Derived orbital radii

\[ r_1 = 6678\,\text{km}, \qquad r_2 = 7378\,\text{km}, \qquad a_t = \frac{6678 + 7378}{2} = 7028\,\text{km} \]

Step-by-step results

Quantity Value Meaning
Initial circular velocity, \(v_{c1}\) 7725.84 m/s Velocity in the 300 km circular orbit
Transfer velocity at perigee, \(v_{t1}\) 7915.88 m/s Velocity immediately after the first burn
First burn, \(\Delta v_1\) 190.04 m/s Injection from the circular orbit into the transfer ellipse
Transfer velocity at apogee, \(v_{t2}\) 7164.85 m/s Velocity on the transfer ellipse at the higher orbit radius
Final circular velocity, \(v_{c2}\) 7350.21 m/s Required circular velocity at 1000 km altitude
Second burn, \(\Delta v_2\) 185.36 m/s Circularization at the higher orbit
Total \(\Delta v\) 375.40 m/s Total maneuver cost
Transfer time 48.86 minutes Half the period of the transfer ellipse

In physical terms, the first burn increases orbital energy by placing the spacecraft onto an ellipse, while the second burn adds the remaining energy needed to circularize at the higher orbit. The total \(\Delta v\) is relatively modest because the energy difference between these two circular orbits is also modest.

Python for the LEO Orbit-Raising Case

import math

# Constants
mu = 3.986004418e14      # Earth's gravitational parameter [m^3/s^2]
R_earth = 6378e3         # Earth radius [m]

# Orbit radii
r1 = R_earth + 300e3     # 300 km LEO
r2 = R_earth + 1000e3    # 1000 km orbit

# Transfer ellipse semi-major axis
a_t = 0.5 * (r1 + r2)

# Velocities
v_c1 = math.sqrt(mu / r1)
v_c2 = math.sqrt(mu / r2)
v_t1 = math.sqrt(mu * (2 / r1 - 1 / a_t))
v_t2 = math.sqrt(mu * (2 / r2 - 1 / a_t))

# Delta-v values
delta_v1 = v_t1 - v_c1
delta_v2 = v_c2 - v_t2
delta_v_total = delta_v1 + delta_v2

# Transfer time
transfer_time = math.pi * math.sqrt(a_t**3 / mu)

# Print results
print(f"Initial circular velocity   = {v_c1:.2f} m/s")
print(f"Transfer velocity at r1     = {v_t1:.2f} m/s")
print(f"First burn Δv1              = {delta_v1:.2f} m/s")
print(f"Transfer velocity at r2     = {v_t2:.2f} m/s")
print(f"Final circular velocity     = {v_c2:.2f} m/s")
print(f"Second burn Δv2             = {delta_v2:.2f} m/s")
print(f"Total Δv                    = {delta_v_total:.2f} m/s")
print(f"Transfer time               = {transfer_time/60:.2f} min")

This explicit form is useful when each calculation should remain visible for teaching and debugging.

Example 2: GEO Transfer

We now examine the classical geostationary transfer problem: moving from a 300 km Earth parking orbit to a geostationary circular orbit.

Initial orbit

300 km Earth parking orbit

Final orbit

GEO circular orbit

Initial orbit radius

\(r_1 = 6678\,\text{km}\)

GEO radius

\(r_2 = 42164\,\text{km}\)

Transfer ellipse

\[ a_t = \frac{6678 + 42164}{2} = 24421\,\text{km} \]

Step-by-step results

Quantity Value Meaning
Initial circular velocity, \(v_{c1}\) 7725.84 m/s Velocity in the parking orbit
Transfer velocity at perigee, \(v_{t1}\) 10151.61 m/s Velocity after injection into the transfer ellipse
First burn, \(\Delta v_1\) 2425.77 m/s Large energy increase required to reach GEO altitude
Transfer velocity at apogee, \(v_{t2}\) 1607.83 m/s Velocity on the transfer ellipse at GEO radius
Final circular velocity, \(v_{c2}\) 3074.67 m/s Velocity required for geostationary circular motion
Second burn, \(\Delta v_2\) 1466.84 m/s Circularization at GEO altitude
Total \(\Delta v\) 3892.61 m/s Total cost of the Earth-to-GEO Hohmann transfer
Transfer time 5.28 hours Half the period of the transfer ellipse

This case shows clearly that reaching geostationary orbit is primarily an energy-raising maneuver. The first burn is large because the orbit must be stretched dramatically, and the second burn remains substantial because the spacecraft arrives at GEO radius with insufficient speed for circular motion.

The dominant effect is not simply the increase in altitude, but the large increase in specific orbital energy required to move from a low Earth parking orbit to a high circular orbit.

Python for the Earth Parking Orbit to GEO Case

import math

# Constants
mu = 3.986004418e14      # Earth's gravitational parameter [m^3/s^2]
R_earth = 6378e3         # Earth radius [m]

# Orbit radii
r1 = R_earth + 300e3     # 300 km Earth parking orbit
r2 = 42164e3             # GEO radius from Earth's center [m]

# Transfer ellipse semi-major axis
a_t = 0.5 * (r1 + r2)

# Velocities
v_c1 = math.sqrt(mu / r1)
v_t1 = math.sqrt(mu * (2 / r1 - 1 / a_t))
v_t2 = math.sqrt(mu * (2 / r2 - 1 / a_t))
v_c2 = math.sqrt(mu / r2)

# Delta-v
delta_v1 = v_t1 - v_c1
delta_v2 = v_c2 - v_t2
delta_v_total = delta_v1 + delta_v2

# Transfer time
transfer_time = math.pi * math.sqrt(a_t**3 / mu)

# Output
print(f"Initial circular velocity   = {v_c1:.2f} m/s")
print(f"Transfer velocity at r1     = {v_t1:.2f} m/s")
print(f"First burn Δv1              = {delta_v1:.2f} m/s")
print(f"Transfer velocity at r2     = {v_t2:.2f} m/s")
print(f"Final circular velocity     = {v_c2:.2f} m/s")
print(f"Second burn Δv2             = {delta_v2:.2f} m/s")
print(f"Total Δv                    = {delta_v_total:.2f} m/s")
print(f"Transfer time               = {transfer_time/3600:.2f} hours")

This script keeps the full GEO transfer visible, which makes the physical scale of the burns much more intuitive.

Generic Runner

The two explicit scripts above are good for teaching. This final script shows how a reusable function can evaluate multiple transfer cases in one place. This is the same logic often used in real mission tools, where multiple candidate transfers are compared consistently.

import math


def hohmann_transfer(mu, r1, r2):
    a_t = 0.5 * (r1 + r2)

    v_c1 = math.sqrt(mu / r1)
    v_c2 = math.sqrt(mu / r2)
    v_t1 = math.sqrt(mu * (2 / r1 - 1 / a_t))
    v_t2 = math.sqrt(mu * (2 / r2 - 1 / a_t))

    delta_v1 = abs(v_t1 - v_c1)
    delta_v2 = abs(v_c2 - v_t2)
    delta_v_total = delta_v1 + delta_v2
    transfer_time = math.pi * math.sqrt(a_t**3 / mu)

    return {
        "a_t_km": a_t / 1000,
        "v_c1": v_c1,
        "v_t1": v_t1,
        "delta_v1": delta_v1,
        "v_t2": v_t2,
        "v_c2": v_c2,
        "delta_v2": delta_v2,
        "delta_v_total": delta_v_total,
        "transfer_time_min": transfer_time / 60,
        "transfer_time_hr": transfer_time / 3600,
    }


mu = 3.986004418e14
R_earth = 6378e3

# Example A: 300 km to 1000 km
leo_case = hohmann_transfer(mu, R_earth + 300e3, R_earth + 1000e3)
print("LEO to 1000 km:")
for k, v in leo_case.items():
    print(f"  {k}: {v:.3f}")

print()

# Example B: 300 km to GEO
geo_case = hohmann_transfer(mu, R_earth + 300e3, 42164e3)
print("300 km to GEO:")
for k, v in geo_case.items():
    print(f"  {k}: {v:.3f}")

This form is well suited to backend calculators, engineering comparisons, and later extension to more advanced transfer studies.

Beyond the Equation

The Hohmann transfer is often described as the optimal solution, but that statement is only true within the assumptions stated earlier.

The Hohmann transfer is optimal when impulses are effectively instantaneous, no plane change is required, and no additional mission timing constraint dominates the problem. Outside that regime, a different transfer strategy may be preferable.

Method When it becomes useful
Hohmann transfer Minimum \(\Delta v\) for impulsive coplanar transfer between circular orbits
Bi-elliptic transfer Can outperform Hohmann for sufficiently large orbit-radius ratios
Low-thrust spiral Appropriate when propulsion is continuous and thrust is limited

Most importantly, \(\Delta v\) should not be viewed as only a velocity increment. It is a practical measure of orbital energy change. That is why the Earth-to-GEO example is so costly: the orbit is not merely being raised in altitude — it is being shifted into a much higher orbital-energy state.

Takeaway

The Hohmann transfer is more than a standard derivation. It is a compact example of engineering reasoning in astrodynamics.

It shows how assumptions shape a model, how a model produces a solution, how that solution can be encoded in Python, and how the final answer must still be interpreted in mission design terms.

For modest orbit raising, such as 300 km to 1000 km, the maneuver cost is relatively small and physically intuitive. For Earth parking orbit to GEO, the same framework reveals a much more demanding energy transition. In both cases, the mathematics is the same — but the engineering meaning becomes very different.

The real lesson is not the equation alone. It is the mapping between physics → mathematics → computation → mission design. That is precisely why this problem belongs in a Problem Bank built around deeper reasoning: it does not merely teach a formula. It teaches how to think through orbital transfer design.